package com.onepiece.gen.frame.aop;

import com.baomidou.dynamic.datasource.processor.DsProcessor;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.PatternMatchUtils;

import javax.annotation.Nonnull;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @Author Administrator
 * Date 2022/11/30 17:50
 * Version 1.0
 * Description 根据目录自动分包
 **/
@Slf4j
public class DynamicDatasourcePackageInterceptor implements MethodInterceptor {
    private static final String DYNAMIC_PREFIX = "#";
    private final Map<String, String> nameMap = new HashMap<>();
    private final DsProcessor dsProcessor;

    public DynamicDatasourcePackageInterceptor(DsProcessor dsProcessor) {
        this.dsProcessor = dsProcessor;
    }
    @Nullable
    @Override
    public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
        String dsKey = determineDatasourceKey(invocation);
        DynamicDataSourceContextHolder.push(dsKey);
        try {
            return invocation.proceed();
        } finally {
            DynamicDataSourceContextHolder.poll();
        }
    }

    private String determineDatasourceKey(MethodInvocation invocation) {
        String key = findDsKey(invocation);
        return (key != null && key.startsWith(DYNAMIC_PREFIX)) ? dsProcessor.determineDatasource(invocation, key) : key;
    }

    private String findDsKey(MethodInvocation invocation) {
        Method method = invocation.getMethod();
        if (!ClassUtils.isUserLevelMethod(method)) {
            return null;
        }

        // Look for direct name match.
        String packageName = invocation.getThis().getClass().getPackage().getName();
        if(!packageName.startsWith("com.onepiece.gen")){
            return null;
        }
        String dsName = filterDsName(packageName);

        if (StringUtils.isBlank(dsName)) {
            return null;
        }

        return dsName;
    }

    private String filterDsName(String packageName){
        if(StringUtils.isBlank(packageName)){
            return null ;
        }
        if(this.nameMap.containsKey(packageName)){
            return this.nameMap.get(packageName);
        }else {
            return filterDsName(packageName.substring(0,packageName.lastIndexOf(".")));
        }
    }

    private boolean isMatch(String methodName, String mappedName) {
        return PatternMatchUtils.simpleMatch(mappedName, methodName);
    }


    /**
     * add PatternMap
     *
     * @param map namedMap
     */
    public void addPatternMap(Map<String, String> map) {
        for (Map.Entry<String, String> item : map.entrySet()) {
            addPattern(item.getKey(), item.getValue());
        }
    }
    /**
     * add Item Pattern
     *
     * @param packageName like select*
     * @param dsKey      like master or slave
     */
    public void addPattern(@Nonnull String packageName, @Nonnull String dsKey) {
        log.debug("dynamic-datasource adding ds package [" + packageName + "] with attribute [" + dsKey + "]");
        nameMap.put(packageName, dsKey);
    }

    /**
     * config from properties
     * <pre>
     *         Properties attributes = new Properties();
     *         attributes.setProperty("select*", "slave");
     *         attributes.setProperty("add*", "master");
     *         attributes.setProperty("update*", "master");
     *         attributes.setProperty("delete*", "master");
     * </pre>
     *
     * @param properties ds properties
     */
    public void fromProperties(@Nonnull Properties properties) {
        Enumeration<?> propNames = properties.propertyNames();
        while (propNames.hasMoreElements()) {
            String packageName = (String) propNames.nextElement();
            String value = properties.getProperty(packageName);
            this.addPattern(packageName, value);
        }
    }

}
